<?php
namespace Swork\Server\Task;

use Swork\Service;

/**
 * 任务器
 */
class TaskManager
{
    /**
     * 服务器配置
     * @var array
     */
    private $env;

    /**
     * 所有任务列表
     * @var array
     */
    private $tasks = [];

    /**
     * 正在执行中的任务列表，储存任务开始时间（毫秒）
     * @var array
     */
    private $executes = [];

    /**
     * 已经储存的任务
     * @var array
     */
    private $exists = [];

    /**
     * Timer constructor.
     * @param array $env 服务器配置
     */
    public function __construct(array $env)
    {
        $this->env = $env;
    }

    /**
     * 设置任务列表
     * @param array $tasks
     */
    function setTasks(array $tasks)
    {
        $this->tasks = $tasks;
    }

    /**
     * 执行用户推进来的任务（手工调用：有可能是本地任务，也有可能是分布式任务）
     * @param array $task
     */
    function deliverTask(array $task)
    {
        $this->localExecute($task);
    }

    /**
     * 临时加入本地任务
     * @param int $time 时间间隔
     * @param string $cls 执行的命名空间
     * @param string $name 执行的类名
     * @param int $timeout 运行超时时间
     */
    function addTask(int $time, string $cls, string $name, int $timeout = 10)
    {
        //组装时间器数据
        $data = [
            'cls' => $cls,
            'name' => $name,
            'timeout' => $timeout
        ];

        //判断此任务是否已经在列表中
        if ($this->isExist($time, $cls, $name))
        {
            return;
        }

        //加载定时器
        Service::$server->tick($time, function () use ($data) {
            $this->localExecute($data);
        });
    }

    /**
     * 执行本地投递过来的任务（定时器产生）
     */
    function localTask()
    {
        foreach (($this->tasks['timer'] ?? []) as $time => $items)
        {
            if ($time <= 0)
            {
                continue;
            }
            foreach ($items as $item)
            {
                //判断此任务是否已经在列表中
                if ($this->isExist($time, $item['cls'], $item['name']))
                {
                    return;
                }

                //加入定时器
                Service::$server->tick($time, function () use ($item) {
                    $this->localExecute($item);
                });
            }
        }
    }

    /**
     * 执行子任务
     * @param array $info
     */
    function slaveTask(array $info)
    {
        foreach ($info['args'] as $arg)
        {
            $info['args'] = [$arg];
            $this->localExecute($info);
        }
    }

    /**
     * 完成指定任务
     * @param string $md5
     */
    function finish(string $md5)
    {
        unset($this->executes[$md5]);
    }

    /**
     * 修饰数据
     * @param array $data 原始数据
     * @param string $md5 数据指纹
     * @return string
     */
    function decorateData(array $data, &$md5 = '')
    {
        //产生身份码
        $md5 = md5(serialize($data));

        //组装数据
        $info = [
            'cls' => $data['cls'],
            'name' => $data['name'],
            'args' => $data['args'] ?? [],
            'md5' => $md5
        ];
        return serialize($info);
    }

    /**
     * 把任务推送至本地运行
     * @param array $item 任务
     */
    private function localExecute(array $item)
    {
        $data = $this->decorateData($item, $md5);
        $time = microtime(true);

        //检查是否有相同任务正在运行中
        if ($this->isRunning($md5, $item))
        {
            return;
        }

        //投递任务（成功后放入执行池中,1：表示本地任务，2：分布式任务）
        $tid = Service::$server->task($data);
        if ($tid > 0)
        {
            $this->executes[$md5] = $time;
        }
    }

    /**
     * 检查任务是否正在运行中
     * @param string $md5 任务KEY
     * @param array $item 任务内容
     * @return bool
     */
    private function isRunning(string $md5, array $item)
    {
        if (!isset($this->executes[$md5]))
        {
            return false;
        }
        if (microtime(true) - $this->executes[$md5] > $item['timeout'])
        {
            return false;
        }
        return true;
    }

    /**
     * 检查任务是否已经存在
     * @param int $time 定时间隔
     * @param string $cls 类名
     * @param string $name 方法名
     * @return bool
     */
    private function isExist(int $time, string $cls, string $name)
    {
        $idf = 'task_' . md5($time . $cls . $name);
        if (isset($this->exists[$idf]))
        {
            Service::$logger->alert("The task [$cls::$name]-[$time] exist already");
            return true;
        }
        $this->exists[$idf] = 1;
        return false;
    }
}


